How 2-Axis Joystick Works & Interface with Arduino + Processing

When you hear the term “thumb joystick,” the first image that probably pops into your mind is a game controller. While thumb joysticks are definitely common in gaming, in the world of DIY electronics, they open up a whole universe of exciting possibilities.

You might use one to control the movements of a robot or rover you’ve built, allowing it to move forward, backward, or turn with intuitive thumb movements. Or perhaps you’d like to create a system for smoothly controlling camera movements, where gentle pushes of the joystick result in slow pans or tilts.

Let’s explore how these joystick modules actually work and how you can connect them to an Arduino for your own projects.

Hardware Overview

If you’ve ever played games on a PlayStation controller, you’ll instantly recognize this thumb joystick. It looks and feels very similar to the ones Sony uses in their controllers. What makes this joystick special is that it’s “self-centering” and “spring-loaded.” This means that whenever you stop pushing it and let go, it automatically bounces back to the center position all by itself. The joystick also has a comfortable cup-shaped knob that fits your thumb perfectly, just like the thumbsticks on gaming controllers.

Potentiometers

At the heart of how this joystick works are two 5K potentiometers. If you look at the joystick module closely, you’ll notice two gray box-shaped components on either side—these are the potentiometers. As you move the joystick, you’ll notice that each one only responds to movement in one direction. The potentiometer on one side only detects when you move the stick left or right (X-axis movement), while the other only detects when you move it up or down (Y-axis movement).

PS2 Joystick Module 2 Potentiometers Internal Structure

These potentiometers are connected to the joystick through a mechanical system called a “gimbal mechanism“, which we will discuss a bit later. This clever arrangement ensures that when you move the joystick diagonally, it properly separates that movement into the correct amounts of horizontal and vertical signals.

Momentary Pushbutton switch

PS2 Joystick Module Push Button Switch Internal Structure

Another cool feature of this joystick is the built-in momentary pushbutton switch—it’s that small black box you can see on one side of the joystick module. This switch activates whenever you press down directly on the joystick knob.

If you look closely, you’ll notice that pressing the knob causes a small lever to push down on the switch. This clever design ensures that the lever can operate the switch no matter what position the joystick is in. This means you can push the joystick in any direction and still be able to click it.

Now, let’s explore the thumb joystick module more deeply and understand how it actually works.

How does the Thumb Joystick Module Work?

It’s really fascinating how a joystick can accurately convert even tiny movements of your fingertips into electrical signals that a computer or microcontroller can understand. This precision comes from the joystick’s clever design, which uses two potentiometers and something called a gimbal mechanism.

Gimbal Mechanism

The gimbal mechanism is what allows the joystick to move smoothly in multiple directions. When you move the joystick, the thumb handle moves a thin rod that sits between two rotatable slotted shafts. These shafts form the gimbal system. One shaft controls movement along the X-axis (left and right), while the other controls movement along the Y-axis (up and down).

Analog Joystick Internal Gimbal Structure

When you tilt the joystick forward or backward, the Y-axis shaft rotates. When you move it left or right, the X-axis shaft rotates instead. And here’s where it gets really interesting—when you push the joystick diagonally, both shafts rotate simultaneously.

2 Axis Joystick Working Gimbal Mechanism

Now, how does the joystick convert these physical movements into electrical signals that your Arduino can understand? The answer lies in two special potentiometers.

Each of the rotating shafts in the joystick connects to its own potentiometer. As you move the joystick, the shafts rotate, which causes the potentiometers to rotate as well. Inside each potentiometer is a contact arm (wiper) that slides along a resistive track. When the joystick moves all the way in one direction, this contact arm moves to one end of the track. When you move the joystick in the opposite direction, the arm moves to the other end.

Potentiometer Working In Joystick Module

This changing position creates a changing resistance. By measuring these potentiometer values, the joystick’s exact position can be determined.

Reading analog values from Joystick

The joystick outputs an analog signal with a voltage between 0 and VCC—that represents its position. As you move the joystick along the X-axis from one extreme to the other, the output voltage changes from 0V to VCC. The same happens for the Y-axis. When the joystick is in its center (or resting) position, the output voltage is about half of the supply voltage.

This changing voltage can be read by the Analog-to-Digital Converter (ADC) in a microcontroller to figure out exactly where the joystick is positioned.

When using an Arduino, which has a 10-bit ADC resolution, the analog readings for each axis can range from 0 to 1023. So as you move the joystick along the X-axis, the readings change in a predictable pattern. When you push it all the way to the left, the X-axis reads a value close to 0. When the joystick sits in its center or neutral position, the X-axis reads approximately 512. And when you push the joystick all the way to the right, the X-axis reads close to 1023.

The Y-axis works in exactly the same way. When you push the joystick all the way up, the Y-axis reads a value close to 0. In the center position, it reads around 512. And when you push the joystick all the way down, the Y-axis reads close to 1023.

When you move the joystick diagonally, both X and Y values change at the same time. By reading both the X and Y values together, your program can determine the exact position of the joystick at any moment. For example, if X = 512 and Y = 1023, you know the joystick is being pushed straight down. If X = 0 and Y = 0, it’s being pushed to the top-left corner.

The figure below illustrates the different values you can expect as you move the joystick in various directions.

PS2 Joystick Module Movement Analog Values on Arduino

Thumb Joystick Module Pinout

Let’s look at what each pin on the thumb joystick module does:

Pinout PS2 Joystick Module

GND is the ground pin.

VCC powers the joystick module. Connect it to your positive power supply – usually 5V or 3.3V, depending on what your project needs.

VRx pin provides an analog output signal that corresponds to the horizontal (left-right) movement of the joystick. When you move the joystick all the way to the left, this pin outputs close to 0 volts. When you move it all the way to the right, it outputs a voltage close to whatever your VCC is (5V or 3.3V). When the joystick sits in the middle without being touched, this pin outputs approximately half of your VCC voltage (around 2.5V if you’re using 5V power).

VRy pin works just like VRx, but for the vertical (up-down) movement of the joystick. Moving the joystick all the way up gives you close to 0 volts, while moving it all the way down might give you the maximum voltage (VCC). Again, when the joystick is centered and not being touched, this pin outputs about half of your VCC voltage.

SW pin connects to a built-in pushbutton switch inside the joystick. This button activates when you press down on the joystick knob itself. An important thing to understand about this pin is that, by default, it floats, meaning it doesn’t automatically give clear HIGH or LOW signals on its own. To make the switch work properly, you need to use a pull-up resistor. This can be either the internal pull-up resistor in your Arduino (which you can enable in your code) or an external resistor you add to your circuit. With the pull-up in place, the pin will normally read as HIGH, and when you press down on the joystick, it will change to LOW.

Wiring a Thumb Joystick Module to an Arduino

Let’s connect the joystick module to the Arduino. It’s pretty straightforward once you know which wires go where.

First, we’ll connect the signals from the joystick to the Arduino. Connect the VRx pin (X-axis) to the Arduino’s analog pin A0, and connect the VRy pin (Y-axis) to the Arduino’s analog pin A1. To detect when the joystick’s pushbutton is pressed, connect the SW pin to Arduino digital pin D8.

Next, we’ll hook up the power. Connect the VCC pin to the Arduino’s 5V output, and connect the GND pin to the Arduino’s ground.

Here’s a quick reference table showing all the pin connections:

Joystick ModuleArduino
GNDGND
VCC5V
VRxA0
VRyA1
SW8

The figure below shows how to wire the thumb joystick module to the Arduino.

Arduino Wiring Fritzing Connections with PS2 2 axis Joystick Module

Once everything is wired up correctly, you can begin writing code to read the joystick’s position and detect button presses.

Arduino Example 1 – Reading the Joystick

Here’s a simple Arduino sketch that reads all the inputs from your joystick and displays them on your computer screen through the serial monitor.

// Arduino pin numbers
const int SW_pin = 8; // digital pin connected to switch output
const int X_pin = 0; // analog pin connected to X output
const int Y_pin = 1; // analog pin connected to Y output

void setup() {
  pinMode(SW_pin, INPUT);
  digitalWrite(SW_pin, HIGH);
  Serial.begin(9600);
}

void loop() {
  Serial.print("Switch:  ");
  Serial.print(digitalRead(SW_pin));
  Serial.print(" | ");
  Serial.print("X-axis: ");
  Serial.print(analogRead(X_pin));
  Serial.print(" | ");
  Serial.print("Y-axis: ");
  Serial.print(analogRead(Y_pin));
  Serial.println(" | ");
  delay(200);
}

Once you upload the sketch, you should see the following output appear on the serial monitor.

ps2 joystick module arduino sketch output on serial window

Code Explanation:

At the beginning, we tell the Arduino which pins are connected to our joystick. We’re using digital pin 8 for the button that’s built into the joystick. For tracking the joystick’s movement, we use analog pins A0 and A1. Pin A0 measures left and right movement (X-axis), while pin A1 measures up and down movement (Y-axis).

// Arduino pin numbers
const int SW_pin = 8; // digital pin connected to switch output
const int X_pin = 0; // analog pin connected to X output
const int Y_pin = 1; // analog pin connected to Y output

In the setup() function, we prepare the button to be read properly. We first configure the button pin as an input. We also activate Arduino’s internal pull-up resistor using digitalWrite(pin, value) function. This makes sure that when the button isn’t pressed, the Arduino reads a value of 1 (HIGH), and when it is pressed, it reads a value of 0 (LOW). Finally, we start communication between the Arduino and the computer so we can see the joystick readings in the Serial Monitor.

pinMode(SW_pin, INPUT);
digitalWrite(SW_pin, HIGH);
Serial.begin(9600);

In the loop() function, we continuously read the joystick’s button state and position, then display the values on the Serial Monitor. First, we check if the button is pressed by using digitalRead(SW_pin). If the function returns 1, it means the button is not pressed, and if it returns 0, it means the button is being pressed.

Next, we read the joystick’s position on the X and Y axes using analogRead(X_pin) and analogRead(Y_pin). These functions give us values between 0 and 1023, depending on the joystick’s movement. The closer the value is to 0, the more the joystick is pushed in one direction, while a value near 1023 means it’s pushed in the opposite direction. These readings are then printed on the Serial Monitor in an organized way, so you can easily see how the values change as you move the joystick.

Finally, we add a short delay of 200 milliseconds before taking the next set of readings to prevent the display from updating too fast, making it easier to observe the joystick’s behavior.

Serial.print("Switch:  ");
Serial.print(digitalRead(SW_pin));
Serial.print(" | ");
Serial.print("X-axis: ");
Serial.print(analogRead(X_pin));
Serial.print(" | ");
Serial.print("Y-axis: ");
Serial.print(analogRead(Y_pin));
Serial.println(" | ");
delay(200);

Arduino Example 2 – Animating Joystick Movements In Processing IDE

Let’s create an exciting Arduino project that shows how a joystick module can control animations in the Processing IDE.

When completed, you’ll be able to see a virtual representation of your joystick’s position on your computer screen. As you move the physical joystick, the on-screen circle will move accordingly!

arduino project animating joystick in precessing ide

This is just the beginning – once you understand how this works, you could expand the project to animate game characters, control camera movements for surveillance projects, or even develop controls for remote vehicles.

Arduino Code

For this project, the Arduino needs to send three pieces of information to the computer: The X-axis position, the Y-axis position, and the state of the pushbutton (pressed or not pressed).

The Arduino code is similar to our previous example, but with one important difference: the values are sent with commas between them. These commas act like separators, making it easy for the Processing software to tell where one value ends and the next begins.

When you upload this code to your Arduino, it will continuously send data that looks like this: “512,498,1” (representing X-position, Y-position, button state).

Go ahead and upload this sketch to your Arduino:

int xValue = 0;  // read value of the X axis
int yValue = 0;  // read value of the Y axis
int bValue = 0;  // value of the button reading

void setup() {
  Serial.begin(9600);  // Open the serial port
  pinMode(8, INPUT);   // Configure Pin 2 as input
  digitalWrite(8, HIGH);
}

void loop() {
  // Read analog port values A0 and A1
  xValue = analogRead(A0);
  yValue = analogRead(A1);

  // Read the logic value on pin 2
  bValue = digitalRead(8);

  // We display our data separated by a comma
  Serial.print(xValue, DEC);
  Serial.print(",");
  Serial.print(yValue, DEC);
  Serial.print(",");
  Serial.print(!bValue);

  // We end with a newline character to facilitate subsequent analysis
  Serial.print("\n");

  // Small delay before the next measurement
  delay(10);
}

Processing Code

After uploading the Arduino code, keep your Arduino connected to your computer. Now we need to run the Processing code that will read those comma-separated values and create a visual display.

The Processing code will:

  • Establish a connection with the Arduino
  • Read the incoming data (button state, X-position, Y-position)
  • Use this data to draw a circle on the screen that moves with your joystick
  • Change the circle’s size when you press the joystick button
import processing.serial.*; //import the Serial library
Serial myPort;

int x;    // variable holding the value from A0
int y;    // variable holding the value from A1
int b;    // variable holding the value from digital pin 2
PFont f;  // define the font variable
String portName;
String val;

void setup() {
  size(512, 512);  // window size

  // we are opening the port
  myPort = new Serial(this, Serial.list()[0], 9600);
  myPort.bufferUntil('\n');

  // choose the font and size
  f = createFont("Arial", 16, true);  // Arial, 16px, anti-aliasing
  textFont(f, 16);                    // size 16px
}

// drawing loop
void draw() {
  fill(0);  // set the fill color to black
  clear();  // clean the screen

  fill(255);  // set the fill color to white

  if (b == 1)  // check if the button is pressed
  {
    // draw a larger circle with specified coordinates
    ellipse(x / 2, y / 2, 50, 50);
  } else {
    // we draw a circle with a certain coordinates
    ellipse(x / 2, y / 2, 25, 25);
  }

  // we display data
  text("AnalogX=" + (1023 - x) + " AnalogY=" + (1023 - y), 10, 20);
}


// data support from the serial port
void serialEvent(Serial myPort) {
  // read the data until the newline n appears
  val = myPort.readStringUntil('\n');

  if (val != null) {
    val = trim(val);

    // break up the decimal and new line reading
    int[] vals = int(splitTokens(val, ","));

    // we assign to variables
    x = vals[0];
    y = vals[1];
    b = vals[2];
  }
}

When you run this code in the Processing IDE, you’ll see a window appear. Inside this window, a white circle will move according to the joystick’s position. If you push the joystick up, down, left, or right, the circle will follow those directions. Pressing the joystick button will make the circle larger, visually indicating that the button is active. The top-left corner of the window will also display the joystick’s X and Y axis values, which update in real-time as you move it.

Code Explanation:

Let’s break down the code step by step.

At the start of the program, we import the Serial library, which allows Processing to communicate with the Arduino.

import processing.serial.*; //import the Serial library
Serial myPort;

We then declare variables to store the joystick’s X and Y positions, the button state, and the font used for displaying text.

int x; // variable holding the value from A0
int y; // variable holding the value from A1
int b; // variable holding the value from digital pin 2
PFont f; // define the font variable
String portName;
String val;

In the setup() function, we create a 512 × 512 window to display the joystick movement. The program establishes a connection with the Arduino by opening the first available serial port. If this does not work, you may need to manually select the correct port where your Arduino is connected. After that, a font is chosen for displaying joystick data on the screen.

void setup() {
  size(512, 512);  // window size

  // we are opening the port
  myPort = new Serial(this, Serial.list()[0], 9600);
  myPort.bufferUntil('\n');

  // choose the font and size
  f = createFont("Arial", 16, true);  // Arial, 16px, anti-aliasing
  textFont(f, 16);                    // size 16px
}

The draw() function is responsible for continuously updating the display. First, the screen is cleared to remove the previous frame. Then, a white circle is drawn at the coordinates determined by the joystick’s position. If the joystick button is pressed, the circle is drawn larger. If the button is not pressed, the circle remains smaller. The joystick’s X and Y values are also displayed as text in the top-left corner, so you can see how the readings change in real-time.

void draw() {
  fill(0);  // set the fill color to black
  clear();  // clean the screen

  fill(255);  // set the fill color to white

  if (b == 1)  // check if the button is pressed
  {
    // draw a larger circle with specified coordinates
    ellipse(x / 2, y / 2, 50, 50);
  } else {
    // we draw a circle with a certain coordinates
    ellipse(x / 2, y / 2, 25, 25);
  }

  // we display data
  text("AnalogX=" + (1023 - x) + " AnalogY=" + (1023 - y), 10, 20);
}

The serialEvent() function handles incoming data from the Arduino. It waits for a complete line of data, then reads and processes it. The received values are separated by commas, so we split them into three parts: the X-axis value, the Y-axis value, and the button state. These values are then assigned to the corresponding variables to update the display.

void serialEvent(Serial myPort) {
  // read the data until the newline n appears
  val = myPort.readStringUntil('\n');

  if (val != null) {
    val = trim(val);

    // break up the decimal and new line reading
    int[] vals = int(splitTokens(val, ","));

    // we assign to variables
    x = vals[0];
    y = vals[1];
    b = vals[2];
  }
}